#include "streamAccess.hpp"
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_string.hpp>
#include <catch2/catch_approx.hpp>
#include <array>

using Catch::Approx;
using Catch::Matchers::ContainsSubstring;

using namespace hms;

TEST_CASE( "Inserting and retrieving Eigen types from stream" )
{
	using hms::read;
	using hms::write;
	
	std::stringstream ss;
	
	SECTION( "Vector2s" )
	{
		Vector2s v1 { -0.1, 2.3 };
		write(ss, v1);

		Vector2s v2;
		read(ss, v2);
		REQUIRE( v1 == v2 );
	}
	SECTION( "Dynamically sized Matrix" )
	{
		Matrix2Xs f1 (2,3);
		f1 <<
			0, 2, 4,
			1, 3, 5;
		
		write(ss, f1);

		Matrix2Xs f2 (2,3);
		read(ss, f2);
		REQUIRE( f1 == f2 );
	}
	SECTION( "Fixed-size matrix" )
	{
		MatrixNNs<2,3> m1;
		m1 <<
			0, 2, 4,
			1, 3, 5;
		
		write(ss, m1);
		
		MatrixNNs<2,3> m2;
		read(ss, m2);
		REQUIRE( m1 == m2 );
	}
}


TEST_CASE( "Inserting and retrieving std::vector types from stream"
){
	using hms::read;
	using hms::write;

	std::stringstream ss;
	
	SECTION( "Chars" )
	{
		std::vector<char> a1 { 'a', 'b', 'c', 'd' };
		decltype(a1) a2 ( a1.size() );
		write(ss, a1);
		read(ss, a2);
		REQUIRE( a1 == a2 );
	}
	SECTION( "Integral types" )
	{
		std::vector a1 { 0, 0, 1, 2 };
		decltype(a1) a2 ( a1.size() );
		write(ss, a1);
		read(ss, a2);
		REQUIRE( a1 == a2 );
	}
	SECTION( "Floating point types" )
	{
		std::vector a1 { 0.01, 0.02, 1.03, 2.04 };
		decltype(a1) a2 ( a1.size() );
		write(ss, a1);
		read(ss, a2);
		REQUIRE( a1 == a2 );
	}	
}

TEST_CASE( "Getting next word from stream" )
{
	using hms::getWord;
	
	std::stringstream ss { "  custom{" };
	
	REQUIRE( getWord(ss) == "custom" );
	
	/* numbers don't count as words, use "read<type>" instead */
	ss.str("123 abc");
	REQUIRE( getWord(ss).empty() == true );
}

TEST_CASE( "Reading fundamental types and strings from stream" )
{
	using hms::read;
	
	std::stringstream ss;
	
	SECTION( "No delimiter" )
	{
		ss.str( "abc   0.1  1  0  abc" );
		REQUIRE( read<std::string>( ss ) == "abc" );
		REQUIRE( read<scalar>( ss ) == 0.1 );
		REQUIRE( read<Index >( ss ) == 1 );
		REQUIRE( read<bool  >( ss ) == false );
		REQUIRE_THROWS_WITH( read<scalar>(ss), ContainsSubstring("type") );
	}
	SECTION( "Line breaks" )
	{
		ss.str( "\nabc \n0.1 1\n0   \n  abc\n" );
		REQUIRE( read<std::string>( ss ) == "abc" );
		REQUIRE( read<scalar>( ss ) == 0.1 );
		REQUIRE( read<Index >( ss ) == 1 );
		REQUIRE( read<bool  >( ss ) == false );
		REQUIRE_THROWS_WITH( read<scalar>(ss), ContainsSubstring("type") );
	}
}

TEST_CASE( "Reading Vector2s from stream" )
{
	using hms::read;
	
	std::stringstream ss;
	
	SECTION( "No delimiter" )
	{
		ss.str( "0.1 -0.2 1.1 1.2 1.0" );
		REQUIRE( read<Vector2s>(ss) == Vector2s{ 0.1, -0.2 } );
		REQUIRE( read<Vector2s>(ss) == Vector2s{ 1.1,  1.2 } );
		REQUIRE_THROWS( read<Vector2s>(ss) );
	}
	SECTION( "Line breaks, no delimiter" )
	{
		ss.str( "0.1 -0.2 \n 1.1 \n 1.2" );
		REQUIRE( read<Vector2s>(ss) == Vector2s{ 0.1, -0.2 } );
		REQUIRE( read<Vector2s>(ss) == Vector2s{ 1.1,  1.2 } );
	}
}

TEST_CASE( "Reading dynamic array of integral types from stream" ){
	using hms::read;
	
	std::stringstream ss;
	
	SECTION( "No delimiter" )
	{
		ss.str( "0 0 2 4 8  1 3 5  " );
		std::vector<int> vec (8);
		read(ss, vec);
		REQUIRE( vec == std::vector<int>{ 0, 0, 2, 4, 8, 1, 3, 5 } );
	}
	SECTION( "Line breaks, no delimiter" )
	{
		ss.str( "\n0 0 2 4 8 \n 1 3 5 \n " );
		std::vector<int> vec (8);
		read(ss, vec);
		REQUIRE( vec == std::vector<int>{ 0, 0, 2, 4, 8, 1, 3, 5 } );
	}
};

TEST_CASE( "Reading std::vector<double> from stream" )
{
	using hms::read;
	
	std::stringstream ss;
	ss.str( "0.5 4.33 1.9998 3 1.4 15.9 1.33e-2 1.33e2" );
	
	std::vector<double> v1 (3), v2 (3), v3 (2);

	read(ss, v1);
	read(ss, v2);
	read(ss, v3);
	
	REQUIRE( v1[0] == Approx(0.5) );
	REQUIRE( v1[1] == Approx(4.33) );
	REQUIRE( v1[2] == Approx(1.9998) );
	
	REQUIRE( v2[0] == Approx(3) );
	REQUIRE( v2[1] == Approx(1.4) );
	REQUIRE( v2[2] == Approx(15.9) );
	
	REQUIRE( v3[0] == Approx(1.33e-2) );
	REQUIRE( v3[1] == Approx(1.33e2) );
}

TEST_CASE( "Reading fixed-size matrix from stream" )
{
	using hms::read;
	
	std::stringstream ss;
	
	SECTION( "Base case, standard MatrixNNs" )
	{
		ss.str( " 0  1  2  3  4  5  6  7  8  9 10 11 " );
		
		/* column-major storage */
		MatrixNNs<3,4> m;
		m <<
			0, 3, 6,  9,
			1, 4, 7, 10,
			2, 5, 8, 11;
		
		REQUIRE( read<MatrixNNs<3,4>>( ss ) == m );
	}
	
	/* double values */
	Vector2s v { 0.99, 1.01 }; /* a MatrixNNs<2,1> is a Vector2s */
	ss.str( " 0.99 1.01 " );
	REQUIRE( read<Vector2s>( ss ) == v );
	
	SECTION( "Sequentially reading matrices" )
	{
		ss.str( " 0 1 2 3 4 5 6 7 8" );
		REQUIRE( read<Vector2s>( ss ) == Vector2s{0,1} );
		REQUIRE( read<Vector3s>( ss ) == MatrixNNs<3,1>{2,3,4} );
		Matrix2s m; m <<
			5,7,
			6,8;
		REQUIRE( read<Matrix2s>( ss ) == m );
	}
	
	SECTION( "Sequentially reading matrices with linebreaks" )
	{
		ss.str( "\n\n 0\n 1 \n2\n 3 4 5\n\n 6 7\n 8" );
		REQUIRE( read<MatrixNNs<2,1>>( ss ) == Vector2s{0,1} );
		REQUIRE( read<MatrixNNs<3,1>>( ss ) == MatrixNNs<3,1>{2,3,4} );
		MatrixNNs<2,2> m; m <<
			5,7,
			6,8;
		REQUIRE( read<MatrixNNs<2,2>>( ss ) == m );
	}
	
	SECTION( "Missing coefficients" )
	{
		ss.str( " 0 1 2 3 4 " );
		
		REQUIRE_THROWS( (read<MatrixNNs<2,3>>( ss )) );
		REQUIRE_THROWS_WITH(
			(read<MatrixNNs<2,3>>( ss )), ContainsSubstring( "coefficient" )
		);
	}
	
	SECTION( "Wrong data type" )
	{
		ss.str( "0 1 2 abc" );
		REQUIRE_THROWS( read<MatrixNNs<2,2>>( ss ) );
	}
	
}

TEST_CASE( "Reading dynamic matrix from stream" )
{
	using hms::read;
	
	std::stringstream ss;
	// auto lead  { '{' };
	// auto trail { '}' };
	
	SECTION( "Base case, standard dynamic matrix" )
	{
		ss.str( " 0 1 2 3 4 5 6 7 8 9 10 11 " );
		MatrixNXs<3> f(3,4);
		f <<
			0, 3, 6,  9,
			1, 4, 7, 10,
			2, 5, 8, 11;
		REQUIRE( read<MatrixNXs<3>>(ss, 3, 4) == f );
	}
	SECTION( "dynamic matrix with linebreaks" )
	{
		ss.str( " 0 1 2 \n 3 4 5 \n 6 7 8 \n\n\n 9 \n 10 11" );
		MatrixNXs<3> f(3,4);
		f <<
			0, 3, 6,  9,
			1, 4, 7, 10,
			2, 5, 8, 11;
		REQUIRE( read<MatrixNXs<3>>(ss, 3, 4) == f );
	}
	SECTION( "Missing coefficient" )
	{
		ss.str( " 0 1 2 3 4 5 6 7 8 9 10 " );
		REQUIRE_THROWS_WITH(
			read<MatrixNXs<3>>(ss, 3, 4),
			ContainsSubstring("coefficient")
		);
	}
}

TEST_CASE( "Passing and retrieving objects from and to a string stream" ){
	using hms::toString;
	using hms::read;
	using hms::write;
	
	std::stringstream ss;
	
	SECTION("combining everything"){
		Index    a1 { 1234 };
		scalar  a2 { 0.98 };
		Vector2s  a3 { 0.98, 1.96 };
		IdxVector a4 { 1, 2, 3, 4 };
		std::vector<scalar> a5 { 0.1, 2.3, 4.5, 6.7 };
		std::string a6 { "word" };
		MatrixNNs<2,3> a7; a7 << 1,2,3,4,5,6;
		MatrixNXs<3> a8 (3,3); a8 << 0,1,2,3,4,5,6,7,8;
		
		
		ss << a1 << " ";
		ss << a2 << " ";
		write(ss, a3) << " ";
		write(ss, a4) << " ";
		write(ss, a5) << " ";
		ss << a6 << " ";
		write(ss, a7) << " ";
		write(ss, a8) << " ";
		
		auto b1 { read<Index >( ss ) };
		auto b2 { read<scalar>( ss ) };
		auto b3 { read<Vector2s>( ss ) };
		std::vector<Index> b4 (4);
		read(ss, b4);
		std::vector<scalar> b5 (4);
		read(ss, b5);
		auto b6 { read<std::string>( ss ) };
		auto b7 { read<MatrixNNs<2,3>>( ss ) };
		auto b8 { read<MatrixNXs<3>>( ss, 3, 3 ) };
		
		/* Index */
		REQUIRE( a1 == b1 );
		/* scalar */
		REQUIRE( a2 == Approx(b2) );
		/* Vector2s */
		REQUIRE( a3[0] == Approx(b3[0]) );
		REQUIRE( a3[1] == Approx(b3[1]) );
		/* IdxVector */
		REQUIRE( a4 == b4 );
		/* std::vector<Vector2s> */
		REQUIRE( a5[0] == Approx(b5[0]) );
		REQUIRE( a5[1] == Approx(b5[1]) );
		REQUIRE( a5[2] == Approx(b5[2]) );
		REQUIRE( a5[3] == Approx(b5[3]) );
		/* string */
		REQUIRE( a6 == b6 );
		/* fixed size matrix */
		REQUIRE( a7 == b7 );
		/* half fixed, half dynamic*/
		REQUIRE( a8 == b8 );
	}
}



TEST_CASE( "Converting fundamental types to string" )
{
	using hms::toString;
	REQUIRE( toString( 0 ) == "0" );
	/* floating point numbers are required to use a number of significant
	 * digits of up to hms::writePrecision. Trailing zeros are dropped. */
	REQUIRE( toString( 1.5 ) == "1.5" );
	REQUIRE( toString( true  ) == "1" );
	REQUIRE( toString( false ) == "0" );
}	


TEST_CASE( "Converting array entries to a space-separated string",
	"[toString]"
){
	using hms::toString;
	
	/* integral types, no nesting */
	IdxVector iarr { 0, 2, 4, 6 };
	REQUIRE( toString( iarr ) == "0 2 4 6" );
	
	/* floating point types, no nesting */
	std::vector<double> darr { 0.1, 0.2, 0.3 };
	REQUIRE( toString( darr ) == "0.1 0.2 0.3" );
}

TEST_CASE( "Converting vector to printable string" )
{
	Vector2s v { 3.8, 5.2 };
	
	REQUIRE( hms::toString( v ) == "3.8 5.2" );
}

TEST_CASE( "Converting matrix to printable string" )
{
	MatrixNNs<2,3> m;
	/* column major storage */
	m <<
		1, 3, 5,
		2, 4, 6;
	
	REQUIRE( hms::toString( m ) == "1 2 3 4 5 6" );
}